home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Languguage OS 2
/
Languguage OS II Version 10-94 (Knowledge Media)(1994).ISO
/
gnu
/
dejagnu.lha
/
dejagnu-1.0.1
/
tcl
/
tclGlob.c
< prev
next >
Wrap
C/C++ Source or Header
|
1993-02-14
|
15KB
|
560 lines
/*
* tclGlob.c --
*
* This file provides procedures and commands for file name
* manipulation, such as tilde expansion and globbing.
*
* Copyright 1990-1991 Regents of the University of California
* Permission to use, copy, modify, and distribute this
* software and its documentation for any purpose and without
* fee is hereby granted, provided that the above copyright
* notice appear in all copies. The University of California
* makes no representations about the suitability of this
* software for any purpose. It is provided "as is" without
* express or implied warranty.
*/
#include "tclInt.h"
#include "tclUnix.h"
/*
* The structure below is used to keep track of a globbing result
* being built up (i.e. a partial list of file names). The list
* grows dynamically to be as big as needed.
*/
typedef struct {
char *result; /* Pointer to result area. */
int totalSpace; /* Total number of characters allocated
* for result. */
int spaceUsed; /* Number of characters currently in use
* to hold the partial result (not including
* the terminating NULL). */
int dynamic; /* 0 means result is static space, 1 means
* it's dynamic. */
} GlobResult;
/*
* Declarations for procedures local to this file:
*/
static void AppendResult _ANSI_ARGS_((Tcl_Interp *interp,
char *dir, char *separator, char *name,
int nameLength));
static int DoGlob _ANSI_ARGS_((Tcl_Interp *interp, char *dir,
char *rem));
/*
*----------------------------------------------------------------------
*
* AppendResult --
*
* Given two parts of a file name (directory and element within
* directory), concatenate the two together and append them to
* the result building up in interp.
*
* Results:
* There is no return value.
*
* Side effects:
* Interp->result gets extended.
*
*----------------------------------------------------------------------
*/
static void
AppendResult(interp, dir, separator, name, nameLength)
Tcl_Interp *interp; /* Interpreter whose result should be
* appended to. */
char *dir; /* Name of directory, without trailing
* slash except for root directory. */
char *separator; /* Separator string so use between dir and
* name: either "/" or "" depending on dir. */
char *name; /* Name of file withing directory (NOT
* necessarily null-terminated!). */
int nameLength; /* Number of characters in name. */
{
int dirFlags, nameFlags;
char *p, saved;
/*
* Next, see if we can put together a valid list element from dir
* and name by calling Tcl_AppendResult.
*/
if (*dir == 0) {
dirFlags = 0;
} else {
Tcl_ScanElement(dir, &dirFlags);
}
saved = name[nameLength];
name[nameLength] = 0;
Tcl_ScanElement(name, &nameFlags);
if ((dirFlags == 0) && (nameFlags == 0)) {
if (*interp->result != 0) {
Tcl_AppendResult(interp, " ", dir, separator, name, (char *) NULL);
} else {
Tcl_AppendResult(interp, dir, separator, name, (char *) NULL);
}
name[nameLength] = saved;
return;
}
/*
* This name has weird characters in it, so we have to convert it to
* a list element. To do that, we have to merge the characters
* into a single name. To do that, malloc a buffer to hold everything.
*/
p = (char *) ckalloc((unsigned) (strlen(dir) + strlen(separator)
+ nameLength + 1));
sprintf(p, "%s%s%s", dir, separator, name);
name[nameLength] = saved;
Tcl_AppendElement(interp, p, 0);
ckfree(p);
}
/*
*----------------------------------------------------------------------
*
* DoGlob --
*
* This recursive procedure forms the heart of the globbing
* code. It performs a depth-first traversal of the tree
* given by the path name to be globbed.
*
* Results:
* The return value is a standard Tcl result indicating whether
* an error occurred in globbing. After a normal return the
* result in interp will be set to hold all of the file names
* given by the dir and rem arguments. After an error the
* result in interp will hold an error message.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static int
DoGlob(interp, dir, rem)
Tcl_Interp *interp; /* Interpreter to use for error
* reporting (e.g. unmatched brace). */
char *dir; /* Name of a directory at which to
* start glob expansion. This name
* is fixed: it doesn't contain any
* globbing chars. */
char *rem; /* Path to glob-expand. */
{
/*
* When this procedure is entered, the name to be globbed may
* already have been partly expanded by ancestor invocations of
* DoGlob. The part that's already been expanded is in "dir"
* (this may initially be empty), and the part still to expand
* is in "rem". This procedure expands "rem" one level, making
* recursive calls to itself if there's still more stuff left
* in the remainder.
*/
register char *p;
register char c;
char *openBrace, *closeBrace;
int gotSpecial, result;
char *separator;
/*
* Figure out whether we'll need to add a slash between the directory
* name and file names within the directory when concatenating them
* together.
*/
if ((dir[0] == 0) || ((dir[0] == '/') && (dir[1] == 0))) {
separator = "";
} else {
separator = "/";
}
/*
* When generating information for the next lower call,
* use static areas if the name is short, and malloc if the name
* is longer.
*/
#define STATIC_SIZE 200
/*
* First, find the end of the next element in rem, checking
* along the way for special globbing characters.
*/
gotSpecial = 0;
openBrace = closeBrace = NULL;
for (p = rem; ; p++) {
c = *p;
if ((c == '\0') || (c == '/')) {
break;
}
if ((c == '{') && (openBrace == NULL)) {
openBrace = p;
}
if ((c == '}') && (closeBrace == NULL)) {
closeBrace = p;
}
if ((c == '*') || (c == '[') || (c == '\\') || (c == '?')) {
gotSpecial = 1;
}
}
/*
* If there is an open brace in the argument, then make a recursive
* call for each element between the braces. In this case, the
* recursive call to DoGlob uses the same "dir" that we got.
* If there are several brace-pairs in a single name, we just handle
* one here, and the others will be handled in recursive calls.
*/
if (openBrace != NULL) {
int remLength, l1, l2;
char static1[STATIC_SIZE];
char *element, *newRem;
if (closeBrace == NULL) {
Tcl_ResetResult(interp);
interp->result = "unmatched open-brace in file name";
return TCL_ERROR;
}
remLength = strlen(rem) + 1;
if (remLength <= STATIC_SIZE) {
newRem = static1;
} else {
newRem = (char *) ckalloc((unsigned) remLength);
}
l1 = openBrace-rem;
strncpy(newRem, rem, l1);
p = openBrace;
for (p = openBrace; *p != '}'; ) {
element = p+1;
for (p = element; ((*p != '}') && (*p != ',')); p++) {
/* Empty loop body: just find end of this element. */
}
l2 = p - element;
strncpy(newRem+l1, element, l2);
strcpy(newRem+l1+l2, closeBrace+1);
if (DoGlob(interp, dir, newRem) != TCL_OK) {
return TCL_ERROR;
}
}
if (remLength > STATIC_SIZE) {
ckfree(newRem);
}
return TCL_OK;
}
/*
* If there were any pattern-matching characters, then scan through
* the directory to find all the matching names.
*/
if (gotSpecial) {
DIR *d;
struct dirent *entryPtr;
int l1, l2;
char *pattern, *newDir, *dirName;
char static1[STATIC_SIZE], static2[STATIC_SIZE];
struct stat statBuf;
/*
* Be careful not to do any actual file system operations on a
* directory named ""; instead, use ".". This is needed because
* some versions of UNIX don't treat "" like "." automatically.
*/
if (*dir == '\0') {
dirName = ".";
} else {
dirName = dir;
}
if ((stat(dirName, &statBuf) != 0) || !S_ISDIR(statBuf.st_mode)) {
return TCL_OK;
}
d = opendir(dirName);
if (d == NULL) {
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "couldn't read directory \"",
dirName, "\": ", Tcl_UnixError(interp), (char *) NULL);
return TCL_ERROR;
}
l1 = strlen(dir);
l2 = (p - rem);
if (l2 < STATIC_SIZE) {
pattern = static2;
} else {
pattern = (char *) ckalloc((unsigned) (l2+1));
}
strncpy(pattern, rem, l2);
pattern[l2] = '\0';
result = TCL_OK;
while (1) {
entryPtr = readdir(d);
if (entryPtr == NULL) {
break;
}
/*
* Don't match names starting with "." unless the "." is
* present in the pattern.
*/
if ((*entryPtr->d_name == '.') && (*pattern != '.')) {
continue;
}
if (Tcl_StringMatch(entryPtr->d_name, pattern)) {
int nameLength = strlen(entryPtr->d_name);
if (*p == 0) {
AppendResult(interp, dir, separator, entryPtr->d_name,
nameLength);
} else {
if ((l1+nameLength+2) <= STATIC_SIZE) {
newDir = static1;
} else {
newDir = (char *) ckalloc((unsigned) (l1+nameLength+2));
}
sprintf(newDir, "%s%s%s", dir, separator, entryPtr->d_name);
result = DoGlob(interp, newDir, p+1);
if (newDir != static1) {
ckfree(newDir);
}
if (result != TCL_OK) {
break;
}
}
}
}
closedir(d);
if (pattern != static2) {
ckfree(pattern);
}
return result;
}
/*
* This is the simplest case: just another path element. Move
* it to the dir side and recurse (or just add the name to the
* list, if we're at the end of the path).
*/
if (*p == 0) {
AppendResult(interp, dir, separator, rem, p-rem);
} else {
int l1, l2;
char *newDir;
char static1[STATIC_SIZE];
l1 = strlen(dir);
l2 = l1 + (p - rem) + 2;
if (l2 <= STATIC_SIZE) {
newDir = static1;
} else {
newDir = (char *) ckalloc((unsigned) l2);
}
sprintf(newDir, "%s%s%.*s", dir, separator, p-rem, rem);
result = DoGlob(interp, newDir, p+1);
if (newDir != static1) {
ckfree(newDir);
}
if (result != TCL_OK) {
return TCL_ERROR;
}
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Tcl_TildeSubst --
*
* Given a name starting with a tilde, produce a name where
* the tilde and following characters have been replaced by
* the home directory location for the named user.
*
* Results:
* The result is a pointer to a static string containing
* the new name. This name will only persist until the next
* call to Tcl_TildeSubst; save it if you care about it for
* the long term. If there was an error in processing the
* tilde, then an error message is left in interp->result
* and the return value is NULL.
*
* Side effects:
* None that the caller needs to worry about.
*
*----------------------------------------------------------------------
*/
char *
Tcl_TildeSubst(interp, name)
Tcl_Interp *interp; /* Interpreter in which to store error
* message (if necessary). */
char *name; /* File name, which may begin with "~/"
* (to indicate current user's home directory)
* or "~<user>/" (to indicate any user's
* home directory). */
{
#define STATIC_BUF_SIZE 50
static char staticBuf[STATIC_BUF_SIZE];
static int curSize = STATIC_BUF_SIZE;
static char *curBuf = staticBuf;
char *dir;
int length;
int fromPw = 0;
register char *p;
if (name[0] != '~') {
return name;
}
/*
* First, find the directory name corresponding to the tilde entry.
*/
if ((name[1] == '/') || (name[1] == '\0')) {
dir = getenv("HOME");
if (dir == NULL) {
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "couldn't find HOME environment ",
"variable to expand \"", name, "\"", (char *) NULL);
return NULL;
}
p = name+1;
} else {
struct passwd *pwPtr;
for (p = &name[1]; (*p != 0) && (*p != '/'); p++) {
/* Null body; just find end of name. */
}
length = p-&name[1];
if (length >= curSize) {
length = curSize-1;
}
memcpy((VOID *) curBuf, (VOID *) (name+1), length);
curBuf[length] = '\0';
pwPtr = getpwnam(curBuf);
if (pwPtr == NULL) {
endpwent();
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "user \"", curBuf,
"\" doesn't exist", (char *) NULL);
return NULL;
}
dir = pwPtr->pw_dir;
fromPw = 1;
}
/*
* Grow the buffer if necessary to make enough space for the
* full file name.
*/
length = strlen(dir) + strlen(p);
if (length >= curSize) {
if (curBuf != staticBuf) {
ckfree(curBuf);
}
curSize = length + 1;
curBuf = (char *) ckalloc((unsigned) curSize);
}
/*
* Finally, concatenate the directory name with the remainder
* of the path in the buffer.
*/
strcpy(curBuf, dir);
strcat(curBuf, p);
if (fromPw) {
endpwent();
}
return curBuf;
}
/*
*----------------------------------------------------------------------
*
* Tcl_GlobCmd --
*
* This procedure is invoked to process the "glob" Tcl command.
* See the user documentation for details on what it does.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* See the user documentation.
*
*----------------------------------------------------------------------
*/
/* ARGSUSED */
int
Tcl_GlobCmd(dummy, interp, argc, argv)
ClientData dummy; /* Not used. */
Tcl_Interp *interp; /* Current interpreter. */
int argc; /* Number of arguments. */
char **argv; /* Argument strings. */
{
int i, result, noComplain;
if (argc < 2) {
notEnoughArgs:
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ?-nocomplain? name ?name ...?\"", (char *) NULL);
return TCL_ERROR;
}
noComplain = 0;
if ((argv[1][0] == '-') && (strcmp(argv[1], "-nocomplain") == 0)) {
if (argc < 3) {
goto notEnoughArgs;
}
noComplain = 1;
}
for (i = 1 + noComplain; i < argc; i++) {
char *thisName;
/*
* Do special checks for names starting at the root and for
* names beginning with ~. Then let DoGlob do the rest.
*/
thisName = argv[i];
if (*thisName == '~') {
thisName = Tcl_TildeSubst(interp, thisName);
if (thisName == NULL) {
return TCL_ERROR;
}
}
if (*thisName == '/') {
result = DoGlob(interp, "/", thisName+1);
} else {
result = DoGlob(interp, "", thisName);
}
if (result != TCL_OK) {
return result;
}
}
if ((*interp->result == 0) && !noComplain) {
char *sep = "";
Tcl_AppendResult(interp, "no files matched glob pattern",
(argc == 2) ? " \"" : "s \"", (char *) NULL);
for (i = 1; i < argc; i++) {
Tcl_AppendResult(interp, sep, argv[i], (char *) NULL);
sep = " ";
}
Tcl_AppendResult(interp, "\"", (char *) NULL);
return TCL_ERROR;
}
return TCL_OK;
}